/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.bpmn.listener;

import com.je.bpm.core.model.ActivitiListener;
import com.je.bpm.core.model.FlowElement;
import com.je.bpm.core.model.HasExecutionListeners;
import com.je.bpm.core.model.ImplementationType;
import com.je.bpm.core.model.task.Task;
import com.je.bpm.core.model.task.UserTask;
import com.je.bpm.engine.ActivitiException;
import com.je.bpm.engine.delegate.*;
import com.je.bpm.engine.impl.bpmn.parser.factory.ListenerFactory;
import com.je.bpm.engine.impl.cfg.TransactionContext;
import com.je.bpm.engine.impl.cfg.TransactionListener;
import com.je.bpm.engine.impl.cfg.TransactionState;
import com.je.bpm.engine.impl.context.Context;
import com.je.bpm.engine.impl.delegate.invocation.TaskListenerInvocation;
import com.je.bpm.engine.impl.persistence.entity.TaskEntity;
import com.je.bpm.engine.impl.util.ProcessDefinitionUtil;
import com.je.bpm.core.model.process.Process;

import java.util.List;
import java.util.Map;

public class ListenerNotificationHelper {

    public void executeExecutionListeners(HasExecutionListeners elementWithExecutionListeners, DelegateExecution execution, String eventType) {
        List<ActivitiListener> listeners = elementWithExecutionListeners.getExecutionListeners();
        if (listeners != null && listeners.size() > 0) {
            ListenerFactory listenerFactory = Context.getProcessEngineConfiguration().getListenerFactory();
            for (ActivitiListener activitiListener : listeners) {

                if (eventType.equals(activitiListener.getEvent())) {

                    BaseExecutionListener executionListener = null;

                    if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equalsIgnoreCase(activitiListener.getImplementationType())) {
                        executionListener = listenerFactory.createClassDelegateExecutionListener(activitiListener);
                    } else if (ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION.equalsIgnoreCase(activitiListener.getImplementationType())) {
                        executionListener = listenerFactory.createExpressionExecutionListener(activitiListener);
                    } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equalsIgnoreCase(activitiListener.getImplementationType())) {
                        if (activitiListener.getOnTransaction() != null) {
                            executionListener = listenerFactory.createTransactionDependentDelegateExpressionExecutionListener(activitiListener);
                        } else {
                            executionListener = listenerFactory.createDelegateExpressionExecutionListener(activitiListener);
                        }
                    } else if (ImplementationType.IMPLEMENTATION_TYPE_INSTANCE.equalsIgnoreCase(activitiListener.getImplementationType())) {
                        executionListener = (ExecutionListener) activitiListener.getInstance();
                    }

                    if (executionListener != null) {
                        if (activitiListener.getOnTransaction() != null) {
                            planTransactionDependentExecutionListener(listenerFactory, execution, (TransactionDependentExecutionListener) executionListener, activitiListener);
                        } else {
                            execution.setEventName(eventType); // eventName is used to differentiate the event when reusing an execution listener for various events
                            execution.setCurrentActivitiListener(activitiListener);
                            ((ExecutionListener) executionListener).notify(execution);
                            execution.setEventName(null);
                            execution.setCurrentActivitiListener(null);
                        }
                    }
                }
            }
        }
    }

    protected void planTransactionDependentExecutionListener(ListenerFactory listenerFactory, DelegateExecution execution, TransactionDependentExecutionListener executionListener, ActivitiListener activitiListener) {
        Map<String, Object> executionVariablesToUse = execution.getVariables();
        CustomPropertiesResolver customPropertiesResolver = createCustomPropertiesResolver(activitiListener);
        Map<String, Object> customPropertiesMapToUse = invokeCustomPropertiesResolver(execution, customPropertiesResolver);

        TransactionDependentExecutionListenerExecutionScope scope = new TransactionDependentExecutionListenerExecutionScope(
                execution.getProcessInstanceId(), execution.getId(), execution.getCurrentFlowElement(), executionVariablesToUse, customPropertiesMapToUse);

        addTransactionListener(activitiListener, new ExecuteExecutionListenerTransactionListener(executionListener, scope));
    }

    public void executeTaskListeners(TaskEntity taskEntity, String eventType) {
        if (taskEntity.getProcessDefinitionId() != null) {
            Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId());
            FlowElement flowElement = process.getFlowElement(taskEntity.getTaskDefinitionKey(), true);
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                executeTaskListeners(userTask, taskEntity, eventType);
            }
        }
    }

    public void executeTaskListeners(UserTask userTask, TaskEntity taskEntity, String eventType) {
        for (ActivitiListener activitiListener : userTask.getTaskListeners()) {
            String event = activitiListener.getEvent();
            if (event.equals(eventType) || event.equals(TaskListener.EVENTNAME_ALL_EVENTS)) {
                BaseTaskListener taskListener = createTaskListener(activitiListener);
                if (activitiListener.getOnTransaction() != null) {
                    planTransactionDependentTaskListener(taskEntity.getExecution(), (TransactionDependentTaskListener) taskListener, activitiListener);
                } else {
                    taskEntity.setEventName(eventType);
                    taskEntity.setCurrentActivitiListener(activitiListener);
                    try {
                        Context.getProcessEngineConfiguration().getDelegateInterceptor().handleInvocation(new TaskListenerInvocation((TaskListener) taskListener, taskEntity));
                    } catch (Exception e) {
                        throw new ActivitiException("Exception while invoking TaskListener: " + e.getMessage(), e);
                    } finally {
                        taskEntity.setEventName(null);
                        taskEntity.setCurrentActivitiListener(null);
                    }
                }
            }
        }
    }

    protected BaseTaskListener createTaskListener(ActivitiListener activitiListener) {
        BaseTaskListener taskListener = null;

        ListenerFactory listenerFactory = Context.getProcessEngineConfiguration().getListenerFactory();
        if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equalsIgnoreCase(activitiListener.getImplementationType())) {
            taskListener = listenerFactory.createClassDelegateTaskListener(activitiListener);
        } else if (ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION.equalsIgnoreCase(activitiListener.getImplementationType())) {
            taskListener = listenerFactory.createExpressionTaskListener(activitiListener);
        } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equalsIgnoreCase(activitiListener.getImplementationType())) {
            if (activitiListener.getOnTransaction() != null) {
                taskListener = listenerFactory.createTransactionDependentDelegateExpressionTaskListener(activitiListener);
            } else {
                taskListener = listenerFactory.createDelegateExpressionTaskListener(activitiListener);
            }
        } else if (ImplementationType.IMPLEMENTATION_TYPE_INSTANCE.equalsIgnoreCase(activitiListener.getImplementationType())) {
            taskListener = (TaskListener) activitiListener.getInstance();
        }
        return taskListener;
    }

    protected void planTransactionDependentTaskListener(DelegateExecution execution, TransactionDependentTaskListener taskListener, ActivitiListener activitiListener) {
        Map<String, Object> executionVariablesToUse = execution.getVariables();
        CustomPropertiesResolver customPropertiesResolver = createCustomPropertiesResolver(activitiListener);
        Map<String, Object> customPropertiesMapToUse = invokeCustomPropertiesResolver(execution, customPropertiesResolver);

        TransactionDependentTaskListenerExecutionScope scope = new TransactionDependentTaskListenerExecutionScope(
                execution.getProcessInstanceId(), execution.getId(), (Task) execution.getCurrentFlowElement(), executionVariablesToUse, customPropertiesMapToUse);
        addTransactionListener(activitiListener, new ExecuteTaskListenerTransactionListener(taskListener, scope));
    }

    protected CustomPropertiesResolver createCustomPropertiesResolver(ActivitiListener activitiListener) {
        CustomPropertiesResolver customPropertiesResolver = null;
        ListenerFactory listenerFactory = Context.getProcessEngineConfiguration().getListenerFactory();
        if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equalsIgnoreCase(activitiListener.getCustomPropertiesResolverImplementationType())) {
            customPropertiesResolver = listenerFactory.createClassDelegateCustomPropertiesResolver(activitiListener);
        } else if (ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION.equalsIgnoreCase(activitiListener.getCustomPropertiesResolverImplementationType())) {
            customPropertiesResolver = listenerFactory.createExpressionCustomPropertiesResolver(activitiListener);
        } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equalsIgnoreCase(activitiListener.getCustomPropertiesResolverImplementationType())) {
            customPropertiesResolver = listenerFactory.createDelegateExpressionCustomPropertiesResolver(activitiListener);
        }
        return customPropertiesResolver;
    }

    protected Map<String, Object> invokeCustomPropertiesResolver(DelegateExecution execution, CustomPropertiesResolver customPropertiesResolver) {
        Map<String, Object> customPropertiesMapToUse = null;
        if (customPropertiesResolver != null) {
            customPropertiesMapToUse = customPropertiesResolver.getCustomPropertiesMap(execution);
        }
        return customPropertiesMapToUse;
    }

    protected void addTransactionListener(ActivitiListener activitiListener, TransactionListener transactionListener) {
        TransactionContext transactionContext = Context.getTransactionContext();
        if (TransactionDependentExecutionListener.ON_TRANSACTION_BEFORE_COMMIT.equals(activitiListener.getOnTransaction())) {
            transactionContext.addTransactionListener(TransactionState.COMMITTING, transactionListener);

        } else if (TransactionDependentExecutionListener.ON_TRANSACTION_COMMITTED.equals(activitiListener.getOnTransaction())) {
            transactionContext.addTransactionListener(TransactionState.COMMITTED, transactionListener);

        } else if (TransactionDependentExecutionListener.ON_TRANSACTION_ROLLED_BACK.equals(activitiListener.getOnTransaction())) {
            transactionContext.addTransactionListener(TransactionState.ROLLED_BACK, transactionListener);

        }
    }

}
